 /*******************************************************************************
  * Copyright (c) 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  ******************************************************************************/

 package org.eclipse.core.databinding;

 import org.eclipse.core.databinding.observable.value.IObservableValue;
 import org.eclipse.core.databinding.observable.value.IValueChangeListener;
 import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
 import org.eclipse.core.databinding.observable.value.WritableValue;
 import org.eclipse.core.databinding.util.Policy;
 import org.eclipse.core.internal.databinding.BindingStatus;
 import org.eclipse.core.internal.databinding.Util;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Status;

 /**
  * @since 1.0
  *
  */
 class ValueBinding extends Binding {
     private final UpdateValueStrategy targetToModel;
     private final UpdateValueStrategy modelToTarget;
     private WritableValue validationStatusObservable;
     private IObservableValue target;
     private IObservableValue model;

     private boolean updatingTarget;
     private boolean updatingModel;
     private IValueChangeListener targetChangeListener = new IValueChangeListener() {
         public void handleValueChange(ValueChangeEvent event) {
             if (!updatingTarget && !Util.equals(event.diff.getOldValue(), event.diff.getNewValue())) {
                 doUpdate(target, model, targetToModel, false, false);
             }
         }
     };
     private IValueChangeListener modelChangeListener = new IValueChangeListener() {
         public void handleValueChange(ValueChangeEvent event) {
             if (!updatingModel && !Util.equals(event.diff.getOldValue(), event.diff.getNewValue())) {
                 doUpdate(model, target, modelToTarget, false, false);
             }
         }
     };

     /**
      * @param targetObservableValue
      * @param modelObservableValue
      * @param targetToModel
      * @param modelToTarget
      */
     public ValueBinding(IObservableValue targetObservableValue,
             IObservableValue modelObservableValue,
             UpdateValueStrategy targetToModel, UpdateValueStrategy modelToTarget) {
         super(targetObservableValue, modelObservableValue);
         this.target = targetObservableValue;
         this.model = modelObservableValue;
         this.targetToModel = targetToModel;
         this.modelToTarget = modelToTarget;
         if ((targetToModel.getUpdatePolicy() & (UpdateValueStrategy.POLICY_CONVERT | UpdateValueStrategy.POLICY_UPDATE)) != 0) {
             target.addValueChangeListener(targetChangeListener);
         } else {
             targetChangeListener = null;
         }
         if ((modelToTarget.getUpdatePolicy() & (UpdateValueStrategy.POLICY_CONVERT | UpdateValueStrategy.POLICY_UPDATE)) != 0) {
             model.addValueChangeListener(modelChangeListener);
         } else {
             modelChangeListener = null;
         }
     }

     protected void preInit() {
         validationStatusObservable = new WritableValue(context
                 .getValidationRealm(), Status.OK_STATUS, IStatus.class);
     }

     protected void postInit() {
         if (modelToTarget.getUpdatePolicy() == UpdateValueStrategy.POLICY_UPDATE) {
             updateModelToTarget();
         }
         if (targetToModel.getUpdatePolicy() != UpdateValueStrategy.POLICY_NEVER) {
             validateTargetToModel();
         }
     }

     public IObservableValue getValidationStatus() {
         return validationStatusObservable;
     }

     public void updateTargetToModel() {
         doUpdate(target, model, targetToModel, true, false);
     }

     public void updateModelToTarget() {
         doUpdate(model, target, modelToTarget, true, false);
     }

     /**
      * Incorporates the provided <code>newStats</code> into the
      * <code>multieStatus</code>.
      *
      * @param multiStatus
      * @param newStatus
      * @return <code>true</code> if the update should proceed
      */
     /* package */boolean mergeStatus(MultiStatus multiStatus, IStatus newStatus) {
         if (!newStatus.isOK()) {
             multiStatus.add(newStatus);
             return multiStatus.getSeverity() < IStatus.ERROR;
         }
         return true;
     }

     /*
      * This method may be moved to UpdateValueStrategy in the future if clients
      * need more control over how the source value is copied to the destination
      * observable.
      */
     private void doUpdate(final IObservableValue source,
             final IObservableValue destination,
             final UpdateValueStrategy updateValueStrategy,
             final boolean explicit, final boolean validateOnly) {
         final int policy = updateValueStrategy.getUpdatePolicy();
         final MultiStatus[] statusHolder = { BindingStatus.ok() };
         if (policy != UpdateValueStrategy.POLICY_NEVER) {
             if (policy != UpdateValueStrategy.POLICY_ON_REQUEST || explicit) {
                 source.getRealm().exec(new Runnable () {
                     public void run() {
                         boolean destinationRealmReached = false;
                         try {
                             Object value = source.getValue();
                             IStatus status = updateValueStrategy
                                     .validateAfterGet(value);
                             if (mergeStatus(statusHolder[0], status)) {
                                 final Object convertedValue = updateValueStrategy
                                         .convert(value);
                                 status = updateValueStrategy
                                         .validateAfterConvert(convertedValue);
                                 if (mergeStatus(statusHolder[0], status)) {
                                     if (policy == UpdateValueStrategy.POLICY_CONVERT
                                             && !explicit) {
                                     } else {
                                         status = updateValueStrategy
                                                 .validateBeforeSet(convertedValue);
                                         if (mergeStatus(statusHolder[0], status)) {
                                             if (!validateOnly) {
                                                 destinationRealmReached = true;
                                                 destination.getRealm().exec(
                                                         new Runnable () {
                                                             public void run() {
                                                                 if (destination == target) {
                                                                     updatingTarget = true;
                                                                 } else {
                                                                     updatingModel = true;
                                                                 }
                                                                 try {
                                                                     IStatus setterStatus = updateValueStrategy
                                                                             .doSet(
                                                                                     destination,
                                                                                     convertedValue);

                                                                     mergeStatus(
                                                                             statusHolder[0],
                                                                             setterStatus);
                                                                 } finally {
                                                                     if (destination == target) {
                                                                         updatingTarget = false;
                                                                     } else {
                                                                         updatingModel = false;
                                                                     }
                                                                     setValidationStatus(statusHolder[0]);
                                                                 }
                                                             }
                                                         });
                                             }
                                         }
                                     }
                                 }
                             }
                         } catch (Exception ex) {
                             // This check is necessary as in 3.2.2 Status
 // doesn't accept a null message (bug 177264).
 String message = (ex.getMessage() != null) ? ex
                                     .getMessage() : ""; //$NON-NLS-1$

                             mergeStatus(statusHolder[0], new Status(
                                     IStatus.ERROR, Policy.JFACE_DATABINDING,
                                     IStatus.ERROR, message, ex));
                         } finally {
                             if (!destinationRealmReached) {
                                 setValidationStatus(statusHolder[0]);
                             }

                         }
                     }
                 });
             }
         }
     }

     public void validateModelToTarget() {
         doUpdate(model, target, modelToTarget, true, true);
     }

     public void validateTargetToModel() {
         doUpdate(target, model, targetToModel, true, true);
     }

     private void setValidationStatus(final IStatus status) {
         validationStatusObservable.getRealm().exec(new Runnable () {
             public void run() {
                 validationStatusObservable.setValue(status);
             }
         });
     }
     
     public void dispose() {
         if (targetChangeListener != null) {
             target.removeValueChangeListener(targetChangeListener);
             targetChangeListener = null;
         }
         if (modelChangeListener != null) {
             model.removeValueChangeListener(modelChangeListener);
             modelChangeListener = null;
         }
         target = null;
         model = null;
         super.dispose();
     }

 }

